package uy.org.softtech.cssuite.web.comando;

import static org.springframework.util.Assert.isTrue;
import static org.springframework.util.Assert.notNull;
import static org.springframework.util.StringUtils.hasText;
import static uy.org.softtech.cssuite.helpers.HelperPassword.*;
import static uy.org.softtech.cssuite.helpers.HelperTextos.validaCorreoElectronico;

import java.util.Locale;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import net.tanesha.recaptcha.ReCaptcha;
import net.tanesha.recaptcha.ReCaptchaResponse;

import org.apache.log4j.Logger;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import uy.org.softtech.cssuite.core.Comando;
import uy.org.softtech.cssuite.core.Controlador.Accion;
import uy.org.softtech.cssuite.core.Modelo;
import uy.org.softtech.cssuite.entidad.usuario.Usuario;
import uy.org.softtech.cssuite.excepciones.EntidadNoActualizadaException;
import uy.org.softtech.cssuite.excepciones.EntidadNoEncontradaException;
import uy.org.softtech.cssuite.excepciones.NotificacionNoEnviadaException;
import uy.org.softtech.cssuite.servicios.ServiciosUsuarios;
import uy.org.softtech.cssuite.web.modelo.ModeloCredenciales;
import uy.org.softtech.cssuite.web.modelo.ModeloCredenciales.Paso;

/**
 * Implementación de {@link Comando} encargado de procesar la solicitud de cambio de credenciales
 * 
 * @author fabian.lobo
 * @see Comando#ejecutar(Modelo, BindingResult)
 */
@Component("comando-credenciales")
public class ComandoCredenciales extends Comando {
	
	private Logger log = Logger.getLogger(getClass());
	
	@Resource(name = "servicios-usuarios" )
	private ServiciosUsuarios serviciosUsuarios;
	
	@Resource(name = "manejador-captcha")
	private ReCaptcha manejadorCaptcha;
	
	@Resource(name = "codificador-password")
	private PasswordEncoder codificadorPassword;
	
	/**
	 * Método para verificar que todos los parámetros han sido seteados correctamente.
	 * 
	 * @author fabian.lobo
	 */
	@PostConstruct
	public void inicializacion(){
		super.inicializacion();
		
		notNull(serviciosUsuarios(), "La propiedad [serviciosUsuarios] de [" + getClass().getName() + "] no ha sido establecida.");
		notNull(manejadorCaptcha(), "La propiedad [manejadorCaptcha] de [" + getClass().getName() + "] no ha sido establecida.");
		notNull(codificadorPassword(), "La propiedad [codificadorPassword] de [" + getClass().getName() + "] no ha sido establecida.");
		
		log.info("Comando encargado de manejar los cambios de credenciales de las cuentas de usuario ha sido inicializado.");
	}

	@Override
	public void validaModelo(Modelo modelo, BindingResult errores) {
		notNull(modelo, "El modelo no puede ser nulo.");
		isTrue(modelo instanceof ModeloCredenciales, "El modelo no es el correcto para este comando.");
		
		ModeloCredenciales modeloCredenciales = (ModeloCredenciales) modelo;
		
		notNull(modeloCredenciales.getAccion(), "Accion no puede ser nulo en este modelo.");
		notNull(modeloCredenciales.getLocale(), "Locale no puede ser nulo en este modelo.");
		notNull(modeloCredenciales.getPaso(), "Paso no puede ser nulo en este modelo.");
		
		Locale locale = modeloCredenciales.getLocale();
		Accion accion = modeloCredenciales.getAccion();
		Paso paso = modeloCredenciales.getPaso();
		String email = modeloCredenciales.getUsuario();
		String nueva = modeloCredenciales.getNueva();
		String confirmar = modeloCredenciales.getConfirmar();
				
		switch(accion) {
		case CREDENCIALES:
			switch(paso) {
			case SOLICITAR:
				if(hasText(email)) {
					if(!validaCorreoElectronico(email)) {
						//El usuario debe ser una dirección de correo electrónico
						errores.addError(new FieldError("modeloCredenciales", "usuario", fuenteMensajes().getMessage("mensaje.correoElectronicoInvalido", null, "Por favor, ingrese un correo electrónico válido.", locale)));
					}
				} else {
					//El usuario no puede ser nulo o espacios en blanco
					errores.addError(new FieldError("modeloCredenciales", "usuario", fuenteMensajes().getMessage("mensaje.usuarioRequerido", null, "Por favor, ingrese su usuario (correo electrónico).", locale)));
				}
				break;
			case RESTABLECER:
				if(hasText(nueva)) {
					if(!validaPassword(nueva)) {
						//La nueva contraseña no cumple con las reglas
						errores.addError(new FieldError("modeloCredenciales", "nueva", fuenteMensajes().getMessage("mensaje.passwordInvalida", null, "El texto ingresado no cumple con las reglas requeridas.", locale)));
					}
				} else {
					//La nueva contraseña no puede ser nula o espacios en blanco
					errores.addError(new FieldError("modeloCredenciales", "nueva", fuenteMensajes().getMessage("mensaje.passwordRequerida", null, "Por favor, ingrese su contraseña.", locale)));
				}
				if(hasText(confirmar)) {
					if(!nueva.equals(confirmar)) {
						//La nueva contraseña y la confirmación no son iguales
						errores.addError(new FieldError("modeloCredenciales", "confirmar", fuenteMensajes().getMessage("mensaje.passwordDiferentes", null, "Los textos ingresados no coinciden.", locale)));
					}
				} else {
					//La contraseña debe ser confirmada
					errores.addError(new FieldError("modeloCredenciales", "confirmar", fuenteMensajes().getMessage("mensaje.passwordNoConfirmada", null, "Debe confirmar sus nuevas credenciales.", locale)));
				}
				
				HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
				ReCaptchaResponse respuestaCaptcha = manejadorCaptcha().checkAnswer(request.getRemoteAddr(), modeloCredenciales.getRecaptcha_challenge_field(), modeloCredenciales.getRecaptcha_response_field());
				if(!respuestaCaptcha.isValid()) {
					//Captcha no es válido
					errores.addError(new FieldError("modeloCredenciales", "recaptcha_response_field", respuestaCaptcha.getErrorMessage()));
				}
				break;
			case CAMBIAR:
				notNull(modeloCredenciales.getId(), "ID no puede ser nulo en este modelo.");
				String comentarios = modeloCredenciales.getComentarios();
				
				if(hasText(nueva)) {
					if(!validaPassword(nueva)) {
						//La nueva contraseña no cumple con las reglas
						errores.addError(new FieldError("modeloCredenciales", "nueva", fuenteMensajes().getMessage("mensaje.passwordInvalida", null, "El texto ingresado no cumple con las reglas requeridas.", locale)));
					}
				} else {
					//La nueva contraseña no puede ser nula o espacios en blanco
					errores.addError(new FieldError("modeloCredenciales", "nueva", fuenteMensajes().getMessage("mensaje.passwordRequerida", null, "Por favor, ingrese su contraseña.", locale)));
				}
				if(hasText(confirmar)) {
					if(!nueva.equals(confirmar)) {
						//La nueva contraseña y la confirmación no son iguales
						errores.addError(new FieldError("modeloCredenciales", "confirmar", fuenteMensajes().getMessage("mensaje.passwordDiferentes", null, "Los textos ingresados no coinciden.", locale)));
					}
				} else {
					//La contraseña debe ser confirmada
					errores.addError(new FieldError("modeloCredenciales", "confirmar", fuenteMensajes().getMessage("mensaje.passwordNoConfirmada", null, "Debe confirmar sus nuevas credenciales.", locale)));
				}
				if(!hasText(comentarios)) {
					//Debe ingresar un comentario
					errores.addError(new FieldError("modeloCredenciales", "comentarios", fuenteMensajes().getMessage("mensaje.comentariosRequeridos", null, "Debe ingresar un comentario de por qué se realiza el cambio.", locale)));
				}
				break;
			}
			break;
		}
	}
	
	@Override
	public void ejecutarComando(Modelo modelo, BindingResult errores) {
		notNull(modelo, "El modelo no puede ser nulo.");
		isTrue(modelo instanceof ModeloCredenciales, "El modelo no es el correcto para este comando.");
		
		ModeloCredenciales modeloCredenciales = (ModeloCredenciales) modelo;
		
		notNull(modeloCredenciales.getAccion(), "Accion no puede ser nulo en este modelo.");
		notNull(modeloCredenciales.getLocale(), "Locale no puede ser nulo en este modelo.");
		notNull(modeloCredenciales.getPaso(), "Paso no puede ser nulo en este modelo.");
		
		Locale locale = modeloCredenciales.getLocale();
		Accion accion = modeloCredenciales.getAccion();
		Paso paso = modeloCredenciales.getPaso();
		String email = modeloCredenciales.getUsuario();
		
		switch (accion) {
		case CREDENCIALES:
			switch(paso) {
				case SOLICITAR:
					try {
						serviciosUsuarios().notificarCambioCredenciales(email, locale);
					} catch (EntidadNoEncontradaException enee) {	//No se encuentra un usuario con ese correo electrónico
						log.error(enee);
						errores.addError(new FieldError("modeloCredenciales", "usuario", enee.getMessage()));
					} catch (NotificacionNoEnviadaException nnee) {	//No se pudo mandar la notificación por correo electrónico
						log.error(nnee);							
						errores.addError(new FieldError("modeloCredenciales", "usuario", nnee.getMessage()));
					}
					break;
				case RESTABLECER:
					try {
						Usuario usuario = serviciosUsuarios().buscarUsuario(email);
						usuario.setPassword(codificadorPassword().encode(modeloCredenciales.getNueva()));
						serviciosUsuarios().actualizar(usuario, SERVICIO);
						
						serviciosUsuarios().remuevoToken(email);
								
					} catch (EntidadNoEncontradaException enee) {
						log.error(enee);
						errores.addError(new ObjectError("modeloCredenciales", fuenteMensajes().getMessage("mensaje.usuarioNoAsociadoAToken", null, "No hay un usuario registrado válido asociado a esta solicitud.", locale)));
					} catch (EntidadNoActualizadaException enae) {
						log.error(enae);
						errores.addError(new ObjectError("modeloCredenciales", fuenteMensajes().getMessage("mensaje.credencialesNoModificadas", null, "No hemos podido actualizar sus credenciales. Por favor, contacte al administrador del sistema.", locale)));
					}
					break;
				case CAMBIAR:
					try {
						Usuario usuario = serviciosUsuarios().buscarUsuario(modeloCredenciales.getId());
						usuario.setPassword(codificadorPassword().encode(modeloCredenciales.getNueva()));
						usuario.setComentarios(modeloCredenciales.getComentarios());
						serviciosUsuarios().actualizar(usuario, SERVICIO);
						
						//Se actualiza el modelo para mostrar el usuario en el mensaje de notificación
						modeloCredenciales.setUsuario(usuario.getUsuario());
					} catch (EntidadNoEncontradaException enee) {
						log.error(enee);
						errores.addError(new ObjectError("modeloCredenciales", enee.getMessage()));
					} catch (EntidadNoActualizadaException enae) {
						log.error(enae);
						errores.addError(new ObjectError("modeloCredenciales", enae.getMessage()));
					}
					break;
			}
			break;
		}		
	}
	
	/*
	 *  GETTERS
	 */
	public ServiciosUsuarios serviciosUsuarios() {
		return serviciosUsuarios;
	}
	public ReCaptcha manejadorCaptcha() {
		return manejadorCaptcha;
	}
	public PasswordEncoder codificadorPassword() {
		return codificadorPassword;
	}
}
